
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.IO;
using System.ServiceModel.Configuration;
using System.Configuration;
using System.Xml.Serialization;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.Diagnostics;

namespace Spike.ContractBuilder
{
    public static class ServiceContractHelper
    {
        static string RESULTS_DIR = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestResults");
        static string ASSEMBLY_DIR = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "ClientAssemblies");
        static string CONFIG_DIR = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "ClientConfiguration");
        static string HOST_DIR = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "HostConfiguration");
        static string FAILURE_DIR = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "FailedToLoad");
        static string BASE_CONFIGURATION = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<configuration>\r\n\t<system.serviceModel>\r\n\t</system.serviceModel>\r\n</configuration>";
        static string LOGS_DIR = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Logs");

        /// <summary>
        /// Retrieves the assemblies located in the assemblies sub-folder
        /// </summary>
        /// <returns></returns>
        internal static Dictionary<string, Assembly> LoadAssemblies()
        {
            Dictionary<string, Assembly> list = new Dictionary<string, Assembly>();
            if (Directory.Exists(ASSEMBLY_DIR))
            {
                foreach (string fileName in Directory.GetFiles(ASSEMBLY_DIR, "*.dll"))
                    list.Add(Path.GetFileNameWithoutExtension(fileName), Assembly.LoadFrom(Path.Combine(Environment.CurrentDirectory, fileName)));
                
                foreach (string fileName in Directory.GetFiles(ASSEMBLY_DIR, "*.exe"))
                    list.Add(Path.GetFileNameWithoutExtension(fileName), Assembly.LoadFrom(Path.Combine(Environment.CurrentDirectory, fileName)));
            }
            else
                Directory.CreateDirectory(ASSEMBLY_DIR);

            return list;
        }

        /// <summary>
        /// Retrieves the contracts locted in the assemblies sub-folder
        /// </summary>
        /// <returns></returns>
        internal static List<ServiceClientContract> LoadClientContracts()
        {
            List<ServiceClientContract> list = new List<ServiceClientContract>();

            if (Directory.Exists(CONFIG_DIR))
            {
                foreach (string fileName in Directory.GetFiles(CONFIG_DIR))
                {
                    ServiceClientContract clientContract = new ServiceClientContract();
                    clientContract.FileName = Path.Combine(Environment.CurrentDirectory, fileName);
                    clientContract.Name = Path.GetFileNameWithoutExtension(fileName);

                    ExeConfigurationFileMap map = new ExeConfigurationFileMap();
                    map.ExeConfigFilename = fileName;
                    System.Configuration.Configuration conf = System.Configuration.ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
                    ServiceModelSectionGroup svcmod = (ServiceModelSectionGroup)conf.GetSectionGroup("system.serviceModel");
                    foreach (ChannelEndpointElement endpoint in svcmod.Client.Endpoints)
                    {
                        clientContract.Endpoints[endpoint.Name] = new ClientEndpoint(endpoint);
                    }

                    if (clientContract.Endpoints.Count > 0)
                        list.Add(clientContract);
                }
            }
            else
                Directory.CreateDirectory(CONFIG_DIR);

            return list;
        }

        /// <summary>
        /// Retrieves the contracts locted in the hosts sub-folder
        /// </summary>
        /// <returns></returns>
        internal static List<ServiceHostContract> LoadHostContracts()
        {
            List<ServiceHostContract> list = new List<ServiceHostContract>();

            if (Directory.Exists(HOST_DIR))
            {
                foreach (string fileName in Directory.GetFiles(HOST_DIR))
                {
                   list.Add(LoadHostContract(fileName));
                }
            }
            else
                Directory.CreateDirectory(HOST_DIR);

            return list;
        }

        internal static ServiceHostContract LoadHostContract(string fileName)
        {
            ServiceHostContract contract = new ServiceHostContract();
            contract.FileName = Path.Combine(Environment.CurrentDirectory, fileName);
            contract.Name = Path.GetFileNameWithoutExtension(fileName);

            ExeConfigurationFileMap map = new ExeConfigurationFileMap();
            map.ExeConfigFilename = fileName;
            System.Configuration.Configuration conf = System.Configuration.ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
            ServiceModelSectionGroup svcmod = (ServiceModelSectionGroup)conf.GetSectionGroup("system.serviceModel");
            foreach (ServiceElement service in svcmod.Services.Services)
            {
                if (service.Host.BaseAddresses.Count > 0)
                {
                    contract.BaseAddress = service.Host.BaseAddresses[0].BaseAddress;
                }

                foreach (ServiceEndpointElement endpoint in service.Endpoints)
                {
                    // save the endpoint
                    contract.Endpoints[endpoint.Name] = new ServiceHostEndpoint(endpoint);
                }
            }
            return contract;
        }

        internal static string AddHost(string name, string configText)
        {
            // should be there, but just in case
            if (!Directory.Exists(HOST_DIR))
                Directory.CreateDirectory(HOST_DIR);

            // copy file to the assembly sub-folder with the same name
            string newFile = Path.Combine(Path.Combine(Environment.CurrentDirectory, HOST_DIR), name + ".config");

            StreamWriter writer = File.CreateText(newFile);

            writer.Write(configText);
            writer.Close();
            writer.Dispose();

            return newFile;
        }

        private static void MoveFileToFailed(string fileFrom, string fileTo)
        {
            if (!Directory.Exists(FAILURE_DIR))
                Directory.CreateDirectory(FAILURE_DIR);

            File.Move(fileFrom, fileTo);
        }

        internal static void MoveFileToFailedToLoad(string fileFrom)
        {
            string fileTo = Path.Combine(FAILURE_DIR, Path.GetFileName(fileFrom));

            MoveFileToFailed(fileFrom, fileTo);
        }
      
        /// <summary>
        /// Copies the assembly from the given file location to the assemblies sub-folder
        /// </summary>
        /// <param name="filePath"></param>
        internal static string AddAssembly(string filePath)
        {
            return AddAssembly(Path.GetFileNameWithoutExtension(filePath), filePath);
        }
        internal static string AddAssembly(string name, string fileFrom)
        {
            // should be there, but just in case
            if (!Directory.Exists(ASSEMBLY_DIR))
                Directory.CreateDirectory(ASSEMBLY_DIR);

            // add extension if missing
            string filename = name.EndsWith(".dll") ? name : name + ".dll";

            // copy file to the assembly sub-folder with the same name
            string toPath = Path.Combine(
                                    Path.Combine(Environment.CurrentDirectory, ASSEMBLY_DIR), filename);
            
            StrongCopyFile(fileFrom, toPath);

            return toPath;
        }
        
        /// <summary>
        /// Give then generated factory, save the assembly and configuration
        /// </summary>
        /// <param name="factory"></param>
        /// <param name="assemblyLocation"></param>
        internal static void SaveClientConfiguration(WcfSamples.DynamicProxy.DynamicProxyFactory factory, string assemblyLocation)
        {
            // should be there, but just in case
            if (!Directory.Exists(CONFIG_DIR))
                Directory.CreateDirectory(CONFIG_DIR);

            // create the file
            string fileName = Path.Combine(CONFIG_DIR, Path.GetFileName(assemblyLocation) + ".config");
            StreamWriter sw = File.CreateText(fileName);
            sw.Write(BASE_CONFIGURATION);
            sw.Close();
            
            ExeConfigurationFileMap map = new ExeConfigurationFileMap();
            map.ExeConfigFilename = fileName;
            System.Configuration.Configuration conf = System.Configuration.ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
            
            ServiceModelSectionGroup group = (ServiceModelSectionGroup)conf.GetSectionGroup("system.serviceModel");            
            foreach(ServiceEndpoint endpoint in factory.Endpoints)
            {
                ChannelEndpointElement element = new ChannelEndpointElement(endpoint.Address, endpoint.Contract.Name);
                element.BindingConfiguration = endpoint.Binding.Name;      
                element.Name = endpoint.Name;

                // TODO: the generation of the binding configuration is not reflecting non-default values.
                //       this needs to be investigated to see if the factory needs updating or ?
                switch (endpoint.Binding.ToString())
                {
                    case "System.ServiceModel.BasicHttpBinding": 
                        BasicHttpBindingElement bhbe = new BasicHttpBindingElement(endpoint.Binding.Name);
                        bhbe.ApplyConfiguration(endpoint.Binding);
                        group.Bindings.BasicHttpBinding.Bindings.Add(bhbe);
                        element.Binding = group.Bindings.BasicHttpBinding.BindingName;
                        break;
                    case "System.ServiceModel.NetTcpBinding":
                        NetTcpBindingElement ntbe = new NetTcpBindingElement(endpoint.Binding.Name);
                        ntbe.ApplyConfiguration(endpoint.Binding);
                        group.Bindings.NetTcpBinding.Bindings.Add(ntbe);
                        element.Binding = group.Bindings.NetTcpBinding.BindingName;
                        break;
                    case "System.ServiceModel.WSHttpBinding":
                        WSHttpBindingElement whbe = new WSHttpBindingElement(endpoint.Binding.Name);
                        whbe.ApplyConfiguration(endpoint.Binding);
                        group.Bindings.WSHttpBinding.Bindings.Add(whbe);
                        element.Binding = group.Bindings.WSHttpBinding.BindingName;
                        break;
                    default:
                        throw new ApplicationException(string.Format("The endpoint binding of {0} is not supported.  Please generate the contract using svcutil.", endpoint.Binding.ToString()));
                }

                group.Client.Endpoints.Add(element);                
            }

            conf.Save();            
        }

        private static void StrongCopyFile(string filefrom, string fileto)
        {
            int i = 0;

            string destination = fileto;

            while(File.Exists(destination))
            {
                i++;
                destination = Path.GetFileNameWithoutExtension(fileto) + i.ToString();
            }

            File.Copy(filefrom, destination);
        }

        internal static string GetTestResulsDirectory()
        {
            if (!Directory.Exists(RESULTS_DIR))
                Directory.CreateDirectory(RESULTS_DIR);

            return RESULTS_DIR;            
        }

        /// <summary>
        /// builds the new filename path
        /// </summary>
        /// <param name="_originalFilename"></param>
        /// <returns></returns>
        internal static string BuildLogFileName(string filename)
        {
            if (!Directory.Exists(LOGS_DIR))
                Directory.CreateDirectory(LOGS_DIR);

            string[] parts = filename.Split(new char[] { '.' });

            switch (parts.Length)
            {
                case 1:
                    return Path.Combine(LOGS_DIR, string.Format("{0} {1}.svclog", parts[0], DateTime.Now.ToString("yyyyMMdd HHmmss")));
                case 2:
                    return Path.Combine(LOGS_DIR, string.Format("{0} {1}.{2}", parts[0], DateTime.Now.ToString("yyyyMMdd HHmmss"), parts[1]));
                default:
                    Trace.TraceWarning(string.Format("ServiceTraceListener failed to BuildFileName with given filename \"{0}\".", filename));
                    return filename;
            }
        }

        internal static void RemoveClientConfiguration(string p)
        {
            if (!Directory.Exists(CONFIG_DIR + "\\Archive"))
                Directory.CreateDirectory(CONFIG_DIR + "\\Archive");

            string toFile = Path.Combine(CONFIG_DIR + "\\Archive", Path.GetFileName(p));

            File.Move(p, toFile);
        }

        internal static void RemoveAssembly(string p)
        {
            if (!Directory.Exists(ASSEMBLY_DIR + "\\Archive"))
                Directory.CreateDirectory(ASSEMBLY_DIR + "\\Archive");

            string toFile = Path.Combine(ASSEMBLY_DIR + "\\Archive", Path.GetFileName(p));

            File.Move(p, toFile);
        }
    }
}
